package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.dao.SkuEsDao;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SkuService;
import com.changgou.util.Result;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @Author tang.qingbo
 * @Date 2020/8/29 14:44
 * @Version 1.0
 */
@Service
public class SkuServiceImpl implements SkuService {

    @Autowired
    private SkuFeign skuFeign;

    @Autowired
    private SkuEsDao skuEsDao;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 导入sku数据
     */
    @Override
    public void importSku() {
        //通过feign调用服务,获取状态正常的sku
        Result<List<Sku>> result = skuFeign.findByStatus("1");
        //获取sku数据
        List<Sku> skuList = result.getData();
        //将数据转换成skuInfo类型的集合
        List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(skuList), SkuInfo.class);
        for (SkuInfo skuInfo : skuInfos) {
            //获取spec
            String spec = skuInfo.getSpec();
            //将spec转换成map类型
            Map<String, Object> specMap = JSON.parseObject(spec, Map.class);
            //设置到skuInfo的specMap属性中
            skuInfo.setSpecMap(specMap);
        }
        //保存所有数据到es中
        skuEsDao.saveAll(skuInfos);
    }

    /**
     * 搜索
     *
     * @param searchMap
     */
    @Override
    public Map search(Map<String, String> searchMap) {
        //1. 构建查询条件
        NativeSearchQueryBuilder nativeSearchQueryBuilder = buildBasicQuery(searchMap);
        //2. 执行查询
        Map<String, Object> resultMap = searchList(nativeSearchQueryBuilder,searchMap);

        //获取分页信息
        resultMap.put("pageNum", nativeSearchQueryBuilder.build().getPageable().getPageNumber() + 1);
        resultMap.put("pageSize", nativeSearchQueryBuilder.build().getPageable().getPageSize());

        /**
         * 1. 用户没有输入分类或者品牌条件的时候,就根据品牌和分类进行分组查询
         * 2. 用户输入了分类或者品牌条件时,就不根据分类或者品牌进行分组查询,因为确定了就不需要显示
         */
        //3. 分类数据==>聚合查询
        //if (searchMap == null || searchMap.get("category") == null) {
        //    List<String> categoryList = searchCategoryList(nativeSearchQueryBuilder);
        //    resultMap.put("categoryList", categoryList);
        //}

        //4. 品牌聚合查询
        //if (searchMap == null || searchMap.get("brand") == null) {
        //    List<String> brandList = searchBrandList(nativeSearchQueryBuilder);
        //    resultMap.put("brandList", brandList);
        //}

        //5. 规格数据查询
        //Map<String, Set<String>> specMap = searchSpecList(nativeSearchQueryBuilder);
        //resultMap.put("specList", specMap);
        //返回数据
        return resultMap;
    }

    /**
     * 查询规格数据
     */
    public Map<String, Set<String>> searchSpecList(NativeSearchQueryBuilder builder) {
        //聚合查询规格数据
        //1.添加分组的域，并且给分组域指定别名  terms("给当前分组取别名").field("分组的域")
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword").size(1000000));
        //执行查询
        AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);

        //获取所有查询数据
        Aggregations aggregations = skuPage.getAggregations();
        //获取规格数据
        StringTerms terms = aggregations.get("skuSpec");

        //定义集合保存数据
        List<String> specList = new ArrayList<>();

        //遍历获取数据并添加到集合中
        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            specList.add(bucket.getKeyAsString());
        }

        //转换格式==>并返回
        return convertSpec(specList);
    }

    /**
     * 将查询到的规格数据list<string>转换成map<string,set>
     */
    public Map<String, Set<String>> convertSpec(List<String> specList) {
        //定义最终返回的数据
        Map<String, Set<String>> resultMap = new HashMap<>();
        if (specList != null) {
            for (String spec : specList) {
                //将规格数据转换成map
                Map<String, String> specMap = JSON.parseObject(spec, Map.class);
                //遍历specMap
                for (Map.Entry<String, String> entry : specMap.entrySet()) {
                    //获取规格名
                    String key = entry.getKey();
                    //规格值
                    String value = entry.getValue();
                    //从resultMap中获取规格值
                    Set<String> specValue = resultMap.get(key);
                    if (specValue == null) {
                        //没获取到,直接创建
                        specValue = new HashSet<>();
                    }
                    //存储value
                    specValue.add(value);
                    //将数据存储到resultMap
                    resultMap.put(key, specValue);
                }
            }
        }
        return resultMap;
    }

    /**
     * 品牌分组搜索,聚合搜索
     */
    public List<String> searchBrandList(NativeSearchQueryBuilder builder) {
        /**
         * 指定分类域,并根据分类域配置聚合查询
         *  参数1: 给查询结果起个别名
         *  参数2: 指定需要聚合查询的域
         */
        //聚合查询==>去重
        builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName").size(1000000));
        //执行查询
        AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);

        //获取查询的所有结果
        Aggregations aggregations = skuPage.getAggregations();

        //获取需要的品牌数据
        StringTerms terms = aggregations.get("skuBrand");

        //定义集合用于封装查询数据
        List<String> brandList = new ArrayList<>();

        //遍历获取结果
        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            brandList.add(bucket.getKeyAsString());
        }

        //返回结果
        return brandList;
    }

    /**
     * 执行分类分组搜索
     */
    public List<String> searchCategoryList(NativeSearchQueryBuilder builder) {

        /**
         * 指定分类域,并根据分类域配置聚合查询
         *      参数1: 给查询结果起别名
         *      参数2: 指定需要聚合查询的列
         */
        //聚合查询条件
        builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName").size(100000));
        //执行查询
        AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);

        //获取所有分组查询的数据
        Aggregations aggregations = skuPage.getAggregations();
        //获取指定分组查询的数据
        StringTerms terms = aggregations.get("skuCategory");

        //定义返回结果数据
        List<String> categoryList = new ArrayList<>();

        //将查询结果封装到list中
        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            //将获取的品牌名称添加至集合中
            categoryList.add(bucket.getKeyAsString());
        }
        //返回结果
        return categoryList;
    }

    /**
     * 构建查询的条件
     */
    public NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap) {
        //创建一个条件构造器
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //构建布尔查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (searchMap != null) {
            //1. 关键字查询
            if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                //nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", searchMap.get("keywords")));
                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")));
            }

            //2. 分类查询
            if (!StringUtils.isEmpty(searchMap.get("category"))) {
                boolQuery.must(QueryBuilders.matchQuery("categoryName", searchMap.get("category")));
            }

            //3. 品牌查询
            if (!StringUtils.isEmpty(searchMap.get("brand"))) {
                boolQuery.must(QueryBuilders.matchQuery("brandName", searchMap.get("brand")));
            }

            //4. 规格查询
            for (String key : searchMap.keySet()) {
                //判断是都带有spec_ 开头的参数
                if (key.startsWith("spec_")) {
                    //为什么要加 .keyword==>因为spec.规格名.keyword 才可以聚合查询
                    boolQuery.must(QueryBuilders.matchQuery("specMap." + key.substring(5) + ".keyword", searchMap.get(key)));
                }
            }

            //5. 价格区间过滤搜索
            String price = searchMap.get("price");
            if (!StringUtils.isEmpty(price)) {
                //传递参数可能是 0-500元 500-100元 3000元以上  ==> 需要替换掉中文字符
                String[] array = price.replace("元", "").replace("以上", "").split("-");

                //array[0] 代表价格下限 array[1] 代表价格上限 array[1] 需要判断,例如3000元以上,没有array[1]
                //价格 > array[0]
                boolQuery.must(QueryBuilders.rangeQuery("price").gt(array[0]));

                //判断是否有价格上限
                if (array.length == 2) {
                    //价格 <= array[1]
                    boolQuery.must(QueryBuilders.rangeQuery("price").lte(array[1]));
                }
            }
        }

        //6. 分页查询
        //获取分页当前页码
        Integer pageNum = getPageNum(searchMap);
        //定义每页展示大小
        int pageSize = 30;
        PageRequest pageRequest = PageRequest.of(pageNum - 1, pageSize);
        //分页
        nativeSearchQueryBuilder.withPageable(pageRequest);

        //7. 排序查询
        //获取排序规则  ASC升序 DESC降序
        if (searchMap != null) {
            String sortRule = searchMap.get("sortRule");
            //获取排序字段
            String sortField = searchMap.get("sortField");
            if (!StringUtils.isEmpty(sortField)) {
                //排序
                nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
            }
        }

        //===================================聚合条件封装================================
        //设置聚合分类
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))) {
            //参数1:查询结果别名 参数2:指定需要聚合的域
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName").size(100000));
        }

        //设置品牌聚合查询
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {
            //参数1:查询结果别名 参数2:指定需要聚合的域
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName").size(100000));
        }

        //设置规格聚合
        //参数1:查询结果别名 参数2:指定需要聚合的域
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("spec").field("spec.keyword").size(100000));

        //=============================================================================

        //添加过滤条件
        nativeSearchQueryBuilder.withQuery(boolQuery);
        return nativeSearchQueryBuilder;
    }

    /**
     * 获取分页数据
     * 无异常,直接返回
     * 有异常,返回 1 ,从第一页开始
     */
    public Integer getPageNum(Map<String, String> searchMap) {
        try {
            return Integer.parseInt(searchMap.get("pageNum"));
        } catch (Exception e) {
            return 1;
        }
    }

    /**
     * 执行查询,数据搜索========高亮搜索
     */
    public Map<String, Object> searchList(NativeSearchQueryBuilder builder,Map<String, String> searchMap) {
        //定义返回结果数据
        Map<String, Object> resultMap = new HashMap<>();

        //设置高亮域
        HighlightBuilder.Field field = new HighlightBuilder
                .Field("name")//高亮位置
                .preTags("<font style='color:red'>")//前缀
                .postTags("</font>")//后缀
                .fragmentSize(100);//设置要显示出来高亮文本的长度

        //开启高亮
        builder.withHighlightFields(field);

        //开始搜索
        AggregatedPage<SkuInfo> skuPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class, new SearchResultMapper() {
            @Override//重新定义返回结果,因为要设置高亮
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                //存储所有结果集
                List<T> list = new ArrayList<>();

                //获取查询到的所有命中数据
                SearchHits hits = response.getHits();
                //获取迭代器
                Iterator<SearchHit> iterator = hits.iterator();
                //遍历结果集
                while (iterator.hasNext()) {
                    //获取当前数据
                    SearchHit hit = iterator.next();
                    //获取字符串结果数据==>json格式  es只能存储json格式数据
                    String sourceAsString = hit.getSourceAsString();
                    //将结果转成skuInfo类型
                    SkuInfo skuInfo = JSON.parseObject(sourceAsString, SkuInfo.class);

                    //获取高亮数据
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    if (highlightFields != null && highlightFields.size()>0) {
                        //获取指定域的高亮数据
                        Text[] texts = highlightFields.get("name").getFragments();
                        //拼接高亮数据
                        StringBuffer buffer = new StringBuffer();
                        if (texts != null) {
                            for (Text text : texts) {
                                buffer.append(text);
                            }
                            //将旧数据替换成高亮数据
                            skuInfo.setName(buffer.toString());
                            //将数据放入集合
                            list.add((T) skuInfo);
                        }
                    }
                }
                //获取原始聚合数据
                Aggregations aggregations = response.getAggregations();
                //返回的 1.集合数据 2.分页数据 3.总记录数 4.聚合数据(重新定义了返回值,要带上之前的聚合查询数据,否者后面查询空指针)
                return new AggregatedPageImpl<>(list, pageable, hits.getTotalHits(), aggregations);
            }
        });

        //===========================获取聚合查询结果============================
        //获取所有的聚合结果
        Aggregations aggregations = skuPage.getAggregations();

        //获取分类数据
        if (StringUtils.isEmpty(searchMap.get("category"))) {
            //没有分类要求,需要展示分类数据  参数1:聚合结果集 参数2:查询时设定的别名
            List<String> categoryList = getGroupData(aggregations, "skuCategory");
            resultMap.put("categoryList", categoryList);
        }

        //获取品牌数据
        if (StringUtils.isEmpty(searchMap.get("brand"))) {
            //没有品牌搜索要求,需要展示品牌数据  参数1:聚合结果集 参数2:查询时设定的别名
            List<String> brandList = getGroupData(aggregations, "skuBrand");
            resultMap.put("brandList", brandList);
        }

        //获取规格数据
        List<String> specList = getGroupData(aggregations, "spec");
        //转换规格数据
        Map<String, Set<String>> specMap = convertSpec(specList);
        //放入resultMap
        resultMap.put("specList", specMap);

        //===========================================================================

        //解析结果==>并存储对应数据
        resultMap.put("rows", skuPage.getContent());
        resultMap.put("totalPages", skuPage.getTotalPages());
        resultMap.put("totalElements", skuPage.getTotalElements());
        return resultMap;
    }

    /**
     * 聚合查询结果解析
     */
    public List<String> getGroupData(Aggregations aggregations,String name) {
        //获取指定数据
        StringTerms terms = aggregations.get(name);

        //定义集合用于封装查询数据
        List<String> list = new ArrayList<>();

        //遍历获取结果
        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            list.add(bucket.getKeyAsString());
        }

        //返回结果
        return list;
    }
}
